﻿using System;
using System.Collections.Generic;
using System.Text;

namespace Rage.Base.Abilities
{
    public abstract class CombatTable {
        public static CombatTable NULL = new NullCombatTable();
        protected Character Char;
        protected BossOptions bossOpts;
        protected CombatFactors combatFactors;
        protected Stats StatS { get; set; }
        protected ClassAbility Ability;
        protected bool useForceAccuracy = false;

        public CollectedCharacter CollChar
        {
            get
            {
                return _collChar ?? (_collChar = new CollectedCharacter()
                {
                    BossOpts = bossOpts,
                    CombatFactors = combatFactors,
                    Char = Char,
                    StatS = StatS,
                });
            }
        }
        private CollectedCharacter _collChar = null;

        public bool isMH { get; protected set; }
        public float Miss { get; protected set; }
        public float Dodge { get; protected set; }
        public float Parry { get; protected set; }
        public float Shield { get; protected set; }
        public float Crit { get; protected set; }
        public float Hit { get; protected set; }

        private float _anyLand = 0f;
        private float _anyNotLand = 1f;
        /// <summary>Any attack that lands: 1f - AnyNotLand</summary>
        public float AnyLand { get { return _anyLand; } }
        /// <summary>Any attack that does not land: Dodges, Parries or Misses</summary>
        public float AnyNotLand { get { return _anyNotLand; } }
        private bool _alwaysHit = false;
        public float AnyMiss { get { return (Miss + Dodge + Parry); } }

        protected virtual void Calculate() { _anyNotLand = Dodge + Parry + Miss; _anyLand = 1f - _anyNotLand; }
        protected virtual void CalculateAlwaysHit() {
            _alwaysHit = true;
            Miss = Dodge = Parry = Shield = Crit = 0f;
            Hit = 1f;
            _anyLand = 1f;
            _anyNotLand = 0f;
        }

        protected void Initialize(Character character, Stats stats, CombatFactors cf, BossOptions bo,
            ClassAbility ability, bool ismh, bool alwaysHit)
        {
            Char = character;
            StatS = stats;
            bossOpts = bo;
            combatFactors = cf;
            Ability = ability;
            isMH = ismh;
            useForceAccuracy = Ability != null ? Ability.UseForceTechHit : false;
            // Start a calc
            Reset(alwaysHit);
        }

        protected void Reset(bool alwaysHit) { if (alwaysHit) { CalculateAlwaysHit(); } else { Calculate(); } }
        public void Reset() { if (_alwaysHit) { return; } Reset(false); }
    }

    public class NullCombatTable : CombatTable { public NullCombatTable() { Shield = Crit = Hit = Dodge = Miss = Parry = 0; } }

    public class AttackTable : CombatTable {
        protected override void Calculate() {
            float tableSize = 0f;

            // Miss
            if (useForceAccuracy) {
                Miss = Math.Min(1f - tableSize, isMH ? combatFactors.C_PlayersChanceToMissMh_Force : combatFactors.C_PlayersChanceToMissOh_Force);
            } else {
                Miss = Math.Min(1f - tableSize, isMH ? combatFactors.C_PlayersChanceToMissMh_Basic : combatFactors.C_PlayersChanceToMissOh_Basic);
            }
            tableSize += Miss;
            // Dodge
            if (Ability == null || Ability.CanBeDodged) {
                if (useForceAccuracy) {
                    Dodge = Math.Min(1f - tableSize, isMH ? combatFactors.C_PlayersMhChanceToBeDodged_Force : combatFactors.C_PlayersOhChanceToBeDodged_Force);
                } else {
                    Dodge = Math.Min(1f - tableSize, isMH ? combatFactors.C_PlayersMhChanceToBeDodged_Basic : combatFactors.C_PlayersOhChanceToBeDodged_Basic);
                }
                tableSize += Dodge;
            } else { Dodge = 0f; }
            // Parry
            if (Ability == null || Ability.CanBeParried) {
                if (useForceAccuracy) {
                    Parry = Math.Min(1f - tableSize, isMH ? combatFactors.C_PlayersMhChanceToBeParried_Force : combatFactors.C_PlayersOhChanceToBeParried_Force);
                } else {
                    Parry = Math.Min(1f - tableSize, isMH ? combatFactors.C_PlayersMhChanceToBeParried_Basic : combatFactors.C_PlayersOhChanceToBeParried_Basic);
                }
                tableSize += Parry;
            } else { Parry = 0f; }
            // Shield
            if (Ability == null || Ability.CanBeShielded) {
                Shield = Math.Min(1f - tableSize, combatFactors.C_PlayersChanceToBeShielded);
                tableSize += Shield;
            } else { Shield = 0f; }
            // Critical Hit
            Crit = 0;
            if ((Ability == null || Ability.CanCrit) && combatFactors.CritProcs != null) {
                float critValueToUse = 0f;
                if (useForceAccuracy) {
                    critValueToUse = Math.Min(1f - tableSize, (isMH ? combatFactors.C_PlayersMhChanceToCritTarget_Force : combatFactors.C_PlayersOhChanceToCritTarget_Force)
                                                                + (Ability == null ? 0f : Ability.BonusCritChance));
                } else {
                    critValueToUse = Math.Min(1f - tableSize, (isMH ? combatFactors.C_PlayersMhChanceToCritTarget_Basic : combatFactors.C_PlayersOhChanceToCritTarget_Basic)
                                                                + (Ability == null ? 0f : Ability.BonusCritChance));
                }
                foreach (WeightedStat ws in combatFactors.CritProcs) {
                    float modCritChance = Math.Min(1f - tableSize, (critValueToUse + StatConversion.GetCritFromRating(ws.Value)) * (1f - Dodge - Miss));
                    Crit += ws.Chance * modCritChance;
                }
                tableSize += Crit;
            }
            // Normal Hit
            Hit = Math.Max(0f, 1f - tableSize);
            base.Calculate();
        }

        public AttackTable() { }

        public AttackTable(Character character, Stats stats, CombatFactors cf, BossOptions bo, ClassAbility ability, bool ismh, bool alwaysHit)
        {
            Initialize(character, stats, cf, bo, ability, ismh, alwaysHit);
        }
    }

    public class DefendTable : CombatTable
    {
        protected override void Calculate()
        {
            float tableSize = 0.0f;
            const float CTCap = 1.00f; // Combat Table Cap (CTC) is set to 100.0% for now, TODO Verify actual combat table cap

            // Miss
            Miss = Math.Min(CTCap - tableSize, CollChar.CombatFactors.C_TargetsChanceToMissThePlayer);
            tableSize += Miss;
            // Dodge
            Dodge = Math.Min(CTCap - tableSize, CollChar.CombatFactors.C_PlayersChanceToDodgeTargetAttacks);
            tableSize += Dodge;
            // Parry
            Parry = Math.Min(CTCap - tableSize, CollChar.CombatFactors.C_PlayersChanceToParryTargetAttacks);
            tableSize += Parry;
            // Shield
            if (CollChar.Char.OffHand != null && CollChar.Char.OffHand.Type == ItemType.Shield)
            {
                Shield = Math.Min(CTCap - tableSize, CollChar.CombatFactors.C_PlayersChanceToShieldTargetAttacks);
                /* The following commented out mechanic is for Shield Block Bonus Uptimes, which we won't calc here for now
                BaseBlock = Shield;
                BuffedBlock = Shield;

                // Average in Shield Shield if Enabled
                float shieldBlockUptime = 10.0f / 2f;//Player.CalcOpts.ShieldBlockInterval;

                BuffedBlock = Math.Min(CTCap - tableSize, CollChar.CombatFactors.C_PlayersChanceToShieldTargetAttacks + 0.25f);
                BuffedCriticalBlock = BuffedBlock * Math.Min(1.0f, (0.25f - (BuffedBlock - Shield)));

                Shield = (Shield * (CTCap - shieldBlockUptime)) + (BuffedBlock * shieldBlockUptime);*/

                tableSize += Shield;
                //BaseBlock -= BaseCriticalBlock;
                //BuffedBlock -= BuffedCriticalBlock;
            }
            // Critical Hit
            Crit = Math.Min(CTCap - tableSize, CollChar.CombatFactors.C_TargetsChanceToCritThePlayer);
            tableSize += Crit;
            // Normal Hit
            Hit = Math.Max(0.0f, CTCap - tableSize);
            base.Calculate();
        }

        public DefendTable(Character character, Stats stats, CombatFactors cf, BossOptions bo, ClassAbility ability, bool ismh, bool alwaysHit)
        {
            Initialize(character, stats, cf, bo, ability, ismh, alwaysHit);
        }
    }
}
